import numpy as np
import pandas as pd
from faker import Faker
import random
from django.utils import timezone
from django.db import transaction
from product.models import BasicProductInfo, ProductsInventory
from utils.IOService import get_product
from abc import ABCMeta, abstractmethod

class BasicGen(metaclass=ABCMeta):
    def __init__(self):
        self.count = None

    @abstractmethod
    def batch_insert(self, batch_size):
        pass

class ProductInfoGen(BasicGen):
    def __init__(self, prd_cnt, ctg_cnt):
        super().__init__()
        self.product_count = prd_cnt
        self.category_count = ctg_cnt
        self._category_list = []
        self._fake = Faker()
        self.count = prd_cnt

    def prop_category_gen(self):
        # 创建产品类别代码（使用字母笛卡尔积）
        if not self._category_list:  # 修改这一行，直接判断列表是否为空
            letters = [chr(i) for i in range(65, 91)]  # A-Z
            product_categories = [f"{a}{b}" for a in letters for b in letters]  # 笛卡尔积 AA-ZZ

            # 随机抽取指定数量的类别
            if self.category_count > len(product_categories):
                raise ValueError(f"请求的类别数量({self.category_count})超过了可用的类别数量({len(product_categories)})")
            self._category_list = np.random.choice(product_categories, size=self.category_count, replace=False).tolist()
        
        return random.choice(self._category_list)

    def prop_category_detail_gen(self):
        choice = random.choice([i+1 for i in range(10)])
        return "{:0>4}".format(choice)

    def prop_price_gen(self):
        """生成商品价格，使用对数分布和本福特定律"""
        # 本福特定律的首位数字概率分布
        benford_probs = {
            1: 0.301,
            2: 0.176,
            3: 0.125,
            4: 0.097,
            5: 0.079,
            6: 0.067,
            7: 0.058,
            8: 0.051,
            9: 0.046
        }
        
        # 根据本福特定律选择首位数字
        first_digit = np.random.choice(
            list(benford_probs.keys()),
            p=list(benford_probs.values())
        )
        
        # 使用对数正态分布生成基础价格范围
        # 设置均值和标准差使大部分价格落在合理区间
        mu = np.log(50)  # 均值取ln(50)，使价格集中在较常见的区间
        sigma = 1.2      # 标准差控制价格分布的分散程度
        
        # 生成基础价格
        base_price = np.random.lognormal(mu, sigma)
        
        # 将基础价格调整为以首位数字开头
        magnitude = int(np.log10(base_price))
        adjusted_price = first_digit * (10 ** magnitude)
        
        # 添加小数部分（符合零售价格习惯）
        decimal_choices = [0.0, 0.5, 0.9, 0.99]
        decimal_weights = [0.2, 0.3, 0.3, 0.2]  # 权重偏向.5和.9
        decimal = np.random.choice(decimal_choices, p=decimal_weights)
        
        # 最终价格调整
        final_price = adjusted_price + decimal
        
        # 确保价格在合理范围内（4.9-9999.99）
        final_price = max(4.9, min(999.99, final_price))
        
        return round(final_price, 2)
        # rules=['rules1','rules1','rules1','rules2','rules2','rules2','rules2','rules2','rules3','rules4']
        # rule=random.choice(rules)
        # common_number = [1,2,3,4,5,6,7,8,9]
        # common_float = [0,5,5,8,8,8,9,9,9,9]

        # if rule == 'rules1':
        # # 规则1
        # # 如果是个位，则不小于4，且小数加上0，或者，5或者8或者9
        #     choice = max(4.9,random.choice(common_number)+random.choice(common_float)*0.1)
        # elif rule== 'rules2':
        # # 规则2
        # # 如果是十位，则减1-9，且小数加上0，或者，5或者8或者9
        #     choice = max(9,random.choice(common_number)*10-random.choice(common_number))+random.choice(common_float)*0.1
        # elif rule=='rules3':
        # # 规则3
        # # 如果是百位，则减1，或者减去十位数再减1，小数选择0或者9
        #     choice = max(100,random.choice(common_number)*100-random.choice(common_number)*10)+random.choice([0,9])*0.1
        # else:
        # # 规则4
        # # 如果选择了千位，则减去个位数或者十位数（随机选一种），且不加小数
        #     choice = random.choice(common_number)*1000-random.choice([random.choice([12,1]),random.choice(common_number)*10])
        # return choice

    def prop_code_gen(self):
        return str(self._fake.random_number(digits=4))

    def prop_name_gen(self):
        # 生成2-3个单词的组合
        word_count = random.choice([2, 3])
        words = [self._fake.word() for _ in range(word_count)]
        return ' '.join(words).title()  # 首字母大写

    def prop_status_gen(self):
        return random.choice(['normal','normal','normal','normal','forbid'])

    def prop_retail_type_gen(self):
        return random.choice(['package','package','package','package','bulk'])

    def prop_unit_gen(self):
        return random.choice([str(i+1) for i in range(10)])

    def prop_sales_point_gen(self):
        """
        生成商品热销率（0-1之间）
        使用Beta分布生成，确保约20%的商品具有较高的热销率（>0.7）
        而80%的商品具有较低的热销率（<0.7）
        """
        # 使用Beta分布，alpha=2, beta=5可以产生符合要求的分布
        # 这个参数设置会使得大约20%的值大于0.7
        sales_point = np.random.beta(2, 5)
        return round(sales_point, 4)  # 保留4位小数

    def product_data_generator(self):
        category = self.prop_category_gen()
        category_detail = ''.join([category,self.prop_category_detail_gen()])
        price = self.prop_price_gen()
        code = ''.join([category_detail,self.prop_code_gen()])
        name = self.prop_name_gen()
        status = self.prop_status_gen()
        retail_type = self.prop_retail_type_gen()
        unit = self.prop_unit_gen()
        sales_point = self.prop_sales_point_gen()  # 添加热销率生成
        return {
            'product_code': code,
            'product_name': name,
            'status': status,
            'category_code': category,
            'category_detail_code': category_detail,
            'retail_type_code': retail_type,
            'unit': unit,
            'default_retail_price': price,
            'sales_point': sales_point,  # 添加热销率字段
            'created_at': timezone.now()
        }

        
    def batch_insert(self, batch_size):
        """批量插入数据"""
        products = []
        for _ in range(batch_size):
            product_data = self.product_data_generator()
            products.append(BasicProductInfo(**product_data))
        
        with transaction.atomic():
            BasicProductInfo.objects.bulk_create(products)
        return len(products)

class ProductIvtGen(BasicGen):
    def __init__(self):
        super().__init__()
        self.basic_product = get_product()
        self.inventory_init()
        self.count = len(self.basic_product)

    def vector_inv_basic_times_price_base(self, default_retail_price):
        # 如果是价格在[0,10]之间，则基数赋予10～50
        if 0<default_retail_price<=10 :
            return np.random.randint(low=10, high=50)
        # 如果是价格在[10,100]之间，则基数赋予10～20
        elif 10<default_retail_price<=100 :
            return np.random.randint(low=10, high=20)
        # 如果是价格在[100,500]之间，则基数赋予5~10
        elif 100<default_retail_price<=500 :
            return np.random.randint(low=5, high=10)
        # 如果是价格在[500,1000]之间，则基数赋予1~5
        elif 500<default_retail_price<=1000:
            return np.random.randint(low=1, high=5)
        else:
            return 1

    def vector_inv_basic_times_retail_type_base(self, retail_type_code):
        # 如果零售类型是包装，则为1
        if retail_type_code == 'package':
            return 1
        # 否则为100
        else:
            return 100

    def inventory_init(self):
        df = self.basic_product
        df['times_v1'] = df['default_retail_price'].map(self.vector_inv_basic_times_price_base)
        df['times_v2'] = df['retail_type_code'].map(self.vector_inv_basic_times_retail_type_base)
        df['instock_quantity'] = df['times_v1']*df['times_v2']*df['unit']
        df['delivery_quantity'] = 0
        df['created_at'] = timezone.localtime()
        df['updated_at'] = timezone.localtime()
        fields = [f.name for f in ProductsInventory._meta.get_fields()]
        self.basic_product = df[fields]

    def batch_insert(self, batch_size=None):  # batch_size参数保留但不使用，保持接口一致性
        """批量插入数据"""
        # 直接使用DataFrame的to_dict方法
        records = self.basic_product.to_dict('records')
        inventory = [ProductsInventory(**record) for record in records]
        
        with transaction.atomic():
            # 使用ignore_conflicts来处理可能的重复插入
            ProductsInventory.objects.bulk_create(
                inventory,
                ignore_conflicts=True  # 忽略唯一约束冲突
            )
        return len(inventory)


